#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <sched.h>
#include "util.h"


struct reader_param{
    void *head;
    size_t iter, len, progress;
    double time;
    bool write;
};

void *reader(void *p){
    struct reader_param volatile *param = (struct reader_param volatile *)p;
    struct timespec begin, end;
    int64_t time = 0;
    void *prev = NULL, *curr;
    size_t iter = param->iter, len = param->len;
    bool write = param->write;

    for(size_t i = 0; i < iter; param->progress = ++i){
        while((curr = param->head) == prev);
        prev = curr;

        if(write){
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);
            pointer_chasing_reverse_16(curr, len/16);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
        } else {
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);
            pointer_chasing_16(curr, len/16);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
        }
        time += (end.tv_sec - begin.tv_sec)*(1000000000) + end.tv_nsec - begin.tv_nsec;
    }

    param -> time = (double)time / iter / len;
    return NULL;
}

int main(int argc, char** argv) {

    size_t burst = 16384, size = (1ull << 28) - burst, line = 64, burst_line = burst/64, burst_count = size/burst;
    bool write = false, dirty = false, random = false, huge_page = true;

    FILE *dataf = NULL;

    for(size_t i = 1; i < argc; i++){
        if(!strcmp(argv[i], "-W")) write = true;
        else if(!strcmp(argv[i], "-D")) dirty = true;
        else if(!strcmp(argv[i], "-R")) random = true;
        else if(!strcmp(argv[i], "-h")) huge_page = false;
        else if(i == argc - 1){
            dataf = fopen(argv[argc - 1], "w+");
            if(!dataf) printf("dataf error\n");
        }
    }
    if(random) set_seed();

    int numberOfProcessors = sysconf(_SC_NPROCESSORS_ONLN);
    printf("Number of processors: %d\n", numberOfProcessors);

    pthread_t pongt;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    void* buffer;
    goto_cpu(0);//alloc buffer at cpu 0
    if(huge_page) huge_page = try_alloc_hp(buffer);
    if(!huge_page) buffer = malloc(size);

    //seq_addr blk_addr(buffer, size, burst, 0);
    //thrash_order_gen blk_ord(blk_addr.size());
    // if(!random) for(size_t k = 0; k < blk_addr.size(); k++){
    //     seq_addr addr(blk_addr[blk_ord[k]], burst, 64, 0);
    //     fill_buf(addr, thrash_order_gen(addr.size()));
    // }
    seq_addr full_addr(buffer, size, line, 0);
    thrash_order_gen full_ord(full_addr.size());
    slice_addr sliced_addr(full_addr, full_ord, burst_line);
    if(!random) for (size_t i = 0; i < burst_count; i++)
    {
        sliced_addr.offset = i * burst_line;
        fill_buf(sliced_addr, seq_order_gen());
    }

    for (size_t i = 0; i < numberOfProcessors; i++)
    {
        goto_cpu(i);

        
        for (size_t j = 0; j < numberOfProcessors; j++)
        {
            if(i==j){
                printf("%05.2lf ", 0.0);
                continue;
            }

            volatile struct reader_param param;
            param.head = NULL;
            param.iter = size / burst;
            param.len = seq_addr(buffer, burst, 64, 0).size();
            param.progress = 0;
            param.write = write;

            goto_cpu(attr, j);
            if(pthread_create(&pongt, &attr, reader, (void*)&param)){
                printf("pong create error\n");
                exit(1);
            }

            if(random) for(size_t k = 0; k < burst_count; k++){
                sliced_addr.offset = k * burst_line;
                fill_buf(sliced_addr, rand_order_gen(burst_line));
            }

            for(size_t k = 0; k < burst_count; k++){
                sliced_addr.offset = k * burst_line;
                param.head = dirty ? pointer_chasing_reverse_16(sliced_addr[0], burst_line/16) : pointer_chasing_16(sliced_addr[0], burst_line/16);
                while (param.progress <= k);
            }

            if(pthread_join(pongt, NULL)){
                printf("pong join error");
                exit(1);
            }

            if(dataf) fprintf(dataf, "%2ld,%2ld,%05.2lf\n", i, j, param.time);
            printf("%05.2lf ", param.time);
            fflush(NULL);
        }
        printf("\n");
    }

    free_hp(buffer);

    return 0;
}
